package com.lezhi.socialgram.service.lbs.impl;

import com.lezhi.socialgram.enums.ResultEnum;
import com.lezhi.socialgram.exception.LbsException;
import com.lezhi.socialgram.exception.SocialGramException;
import com.lezhi.socialgram.model.lbs.LbsCondition;
import com.lezhi.socialgram.model.lbs.LbsUser;
import com.lezhi.socialgram.dto.UserNearly;
import com.lezhi.socialgram.model.sys.SignUpUser;
import com.lezhi.socialgram.model.sys.TelNumSignInUser;
import com.lezhi.socialgram.service.lbs.LbsService;
import com.lezhi.socialgram.util.Param;
import com.lezhi.socialgram.util.Result;
import com.lezhi.socialgram.util.ResultUtils;
import com.lezhi.socialgram.util.XUtil;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.GeoDistanceSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;

/**
 * Created by daizhen on 2017/3/27.
 */
@Service
public class LbsServiceImpl implements LbsService {

    private static final Logger logger = LoggerFactory.getLogger(LbsServiceImpl.class);

    private static String[] fields = {
            "userId", "nicknameZh", "nicknameEn", "sex", "personalSign", "birthdayTimezone",
            "profession", "imageUrl", "imageCount", "voiceGreetingUrl"
    };

    @Autowired
    ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public List<UserNearly> queryPersonNearlyByLatLon(Double latitude, Double longitude, Integer sex) {
        return queryPersonNearly(latitude, longitude, null, sex, 0, null, null, null);
    }

    @Override
    public Result queryPersonNearlyByUserId(LbsCondition condition) {
        List<UserNearly> userNearlyList = queryPersonNearly(condition.getLat(), condition.getLon(), condition.getUserId(), condition.getSex(),
                condition.getPage(), condition.getMinAge(), condition.getMaxAge(), condition.getMaxDistance());
        return ResultUtils.success(userNearlyList);
    }

    @Override
    public String indexUser(SignUpUser signUpUser) {
        try {
            LbsUser lbsUser = new LbsUser(signUpUser.getUserId(), signUpUser.getUserImageUrl(), 1, signUpUser.getNicknameZh(),
                    signUpUser.getNicknameEn(), signUpUser.getSex(), signUpUser.getBirthdayTimezone(), signUpUser.getVoiceGreetingUrl(),
                    signUpUser.getVoiceGreetingSeconds(), "", "", new GeoPoint(signUpUser.getLat(), signUpUser.getLon()), signUpUser.getCountry(),
                    signUpUser.getRegion(), signUpUser.getCity(), signUpUser.getCounty(), ",");
            IndexQuery indexQuery = new IndexQueryBuilder().withObject(lbsUser).build();
            return elasticsearchTemplate.index(indexQuery);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new SocialGramException(ResultEnum.INTERNAL_ERROR);
        }
    }

    @Override
    public String indexUser(TelNumSignInUser signInUser) {
        try {
            GetQuery getQuery = new GetQuery();
            getQuery.setId(signInUser.getUserId().toString());
            LbsUser lbsUser = elasticsearchTemplate.queryForObject(getQuery, LbsUser.class);
            lbsUser.setCountry(signInUser.getCountry());
            lbsUser.setRegion(signInUser.getRegion());
            lbsUser.setCity(signInUser.getCity());
            lbsUser.setCounty(signInUser.getCounty());
            lbsUser.setPoint(new GeoPoint(signInUser.getLat(), signInUser.getLon()));
            String index = elasticsearchTemplate.index(new IndexQueryBuilder().withObject(lbsUser).build());
            return index;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new SocialGramException(ResultEnum.INTERNAL_ERROR);
        }
    }

    @Override
    public UpdateResponse updateUserLocation(Long userId, GeoPoint geoPoint) {
        try {
            IndexRequest indexRequest = new IndexRequest();
            indexRequest.source("point", geoPoint);
            UpdateQuery updateQuery = new UpdateQueryBuilder().withId(userId.toString())
                    .withClass(LbsUser.class).withIndexName("social_gram_user")
                    .withType("user").withIndexRequest(indexRequest).build();

            return elasticsearchTemplate.update(updateQuery);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new SocialGramException(ResultEnum.INTERNAL_ERROR);
        }
    }

    /**
     * @param lat         纬度
     * @param lon         经度
     * @param userId      用户id
     * @param sex         性别
     * @param minAge      最小年龄
     * @param maxAge      最大年龄
     * @param maxDistance 最远距离
     * @return
     */
    private List<UserNearly> queryPersonNearly(Double lat, Double lon, Long userId, Integer sex, Integer page,
                                               Integer minAge, Integer maxAge, Integer maxDistance) {
        try {
            SourceFilter sourceFilter = new FetchSourceFilter(fields, null);
            if (page == null) {
                page = 0;
            } else {
                if (page == 0) {
                    page = 0;
                } else {
                    page -= 1;
                }
            }
            final PageRequest pageRequest = new PageRequest(page, 25);
            GeoDistanceQueryBuilder filter =
                    QueryBuilders.geoDistanceQuery("point").lat(lat).lon(lon).
                            distance(maxDistance == null ? 20 : maxDistance, DistanceUnit.KILOMETERS);

            GeoDistanceSortBuilder sort =
                    SortBuilders.geoDistanceSort("point").unit(DistanceUnit.KILOMETERS).point(lat, lon)
                            .order(SortOrder.ASC);

            NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder()
                    .withFilter(filter)
                    .withSort(sort).withIndices("social_gram_user").withTypes("user").withPageable(pageRequest);

            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();


            if (sex != null) {
                boolQueryBuilder.must(QueryBuilders.termQuery("sex", sex));
            }

            if (userId != null) {
                String querys = "," + userId + ",";
                boolQueryBuilder.mustNot(QueryBuilders.matchQuery("seenMes", querys));
            }

            if (minAge != null && maxAge != null) {
                Calendar minAgeBirthday = Calendar.getInstance();
                minAgeBirthday.add(Calendar.YEAR, -minAge);
                Calendar maxAgeBirthday = Calendar.getInstance();
                maxAgeBirthday.add(Calendar.YEAR, -maxAge);
                long minAgeTimezone = minAgeBirthday.getTime().getTime();
                long maxAgeTimezone = maxAgeBirthday.getTime().getTime();
                System.out.println(minAgeTimezone);
                System.out.println(maxAgeTimezone);
                boolQueryBuilder.must(QueryBuilders.rangeQuery("birthdayTimezone").gte(maxAgeTimezone).lte(minAgeTimezone));
            }

            NativeSearchQuery query = nativeSearchQueryBuilder.withQuery(boolQueryBuilder).build();

            List<UserNearly> userNearlyList = elasticsearchTemplate.query(query, searchResponse -> {
                List<UserNearly> users = new ArrayList<>();
                SearchHits hits = searchResponse.getHits();
                SearchHit[] searchHists = hits.getHits();
                for (SearchHit hit : searchHists) {
                    Map<String, Object> hitMap = hit.getSource();
                    Integer id = (Integer) hit.getSource().get("userId");
                    String nicknameZh = (String) hit.getSource().get("nickNameZh");
                    String nicknameEn = (String) hit.getSource().get("nickNameEh");
                    Integer userSex = (Integer) hit.getSource().get("sex");
                    Long birthdayTimezone = (Long) hit.getSource().get("birthdayTimezone");
                    String personalSign = (String) hit.getSource().get("personalSign");
                    String profession = (String) hit.getSource().get("profession");
                    String imageUrl = (String) hit.getSource().get("imageUrl");
                    Integer imageCount = (Integer) hit.getSource().get("imageCount");
                    String voiceGreetingUrl = (String) hit.getSource().get("voiceGreetingUrl");
                    Integer voiceGreetingSeconds = (Integer) hit.getSource().get("voiceGreetingSeconds");
                    // 获取距离值，并保留两位小数点
                    double distance = new BigDecimal((Double) hit.getSortValues()[0]).
                            setScale(0, BigDecimal.ROUND_HALF_DOWN).doubleValue();
                    //判断英文昵称还是中文昵称
                    String nickName;
                    if (nicknameZh == null) {
                        nickName = nicknameEn;
                    } else {
                        nickName = nicknameZh;
                    }
                    //计算年龄
                    Date birthday = new Date(birthdayTimezone);
                    Integer age = XUtil.getAge(birthday);
                    String distanceDesc = distance + DistanceUnit.KILOMETERS.toString();
                    //计算综合描述
                    String desc;
                    if (!StringUtils.isEmpty(personalSign)) {
                        desc = personalSign;
                    } else {
                        if (!StringUtils.isEmpty(profession)) {
                            desc = profession;
                        } else {
                            desc = distanceDesc;
                        }
                    }
                    UserNearly user = new UserNearly(Long.parseLong(id.toString()),
                            nickName, sex, age, imageUrl,
                            imageCount, voiceGreetingUrl, voiceGreetingSeconds, desc, distanceDesc);
                    Integer likeMeCount = Integer.parseInt(stringRedisTemplate.opsForHash().get(id + "_info", Param.LIKE_COUNT) != null ? stringRedisTemplate.opsForHash().get(id + "_info", Param.LIKE_COUNT) + "" : "0");
                    Integer seenCount = Integer.parseInt(stringRedisTemplate.opsForHash().get(id + "_info", Param.SEEN_COUNT) != null ? stringRedisTemplate.opsForHash().get(id + "_info", Param.SEEN_COUNT) + "" : "0");
                    Double score = 0.5;
                    if (likeMeCount != null && seenCount != null) {
                        if (seenCount > 10) {
                            score = Double.parseDouble(likeMeCount.toString()) / Double.parseDouble(seenCount.toString());
                        }
                    }
                    user.setScore(score);
                    users.add(user);
                }

                return users;
            });

            Collections.sort(userNearlyList, Comparator.comparing(UserNearly::getScore));

            return userNearlyList;

        } catch (Exception e) {
            logger.error("查询附近的人出错{}", e);
            throw new LbsException(ResultEnum.INTERNAL_ERROR);
        }
    }
}
